Fix un-tracked issues & add tests#5
Conversation
… & file reading, add missing tests
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
📝 WalkthroughWalkthroughThis PR performs a systematic maintenance pass removing deprecated future annotations imports, optimizing font and window handle caching in notify.py, improving file reading efficiency in video_recorder.py via deque-based tailing, and adding comprehensive test coverage across eight modules to validate core functionality. ChangesMaintenance and Optimization Pass
🎯 2 (Simple) | ⏱️ ~12 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request removes __future__ imports across the codebase, introduces caching for font loading and window handles in notify.py, and optimizes log reading in video_recorder.py using collections.deque. It also significantly expands the test suite with several new test files. Feedback focuses on the new mandatory wandb dependency in train.py, thread-safety and redundancy issues in the font caching logic, and the need for AsyncMock in the environment tests to correctly handle async iteration. Additionally, using getattr was suggested for more idiomatic attribute access when checking for cached window handles.
| _cached_font = None | ||
| def _get_font(): | ||
| global _cached_font | ||
| if _cached_font is None: | ||
| try: | ||
| from PIL import ImageFont | ||
| _cached_font = ImageFont.truetype("arial.ttf", 24) | ||
| except IOError: | ||
| _cached_font = ImageFont.load_default() | ||
| return _cached_font |
There was a problem hiding this comment.
The font loading logic is not thread-safe and contains a redundant import. Since TelegramNotifier uses a background polling thread, concurrent calls to _get_font could lead to race conditions or multiple font loading attempts. Additionally, ImageFont is already imported at the top of the file (line 22), making the local import unnecessary.
| _cached_font = None | |
| def _get_font(): | |
| global _cached_font | |
| if _cached_font is None: | |
| try: | |
| from PIL import ImageFont | |
| _cached_font = ImageFont.truetype("arial.ttf", 24) | |
| except IOError: | |
| _cached_font = ImageFont.load_default() | |
| return _cached_font | |
| _font_lock = threading.Lock() | |
| _cached_font = None | |
| def _get_font(): | |
| global _cached_font | |
| if _cached_font is None: | |
| with _font_lock: | |
| if _cached_font is None: | |
| try: | |
| _cached_font = ImageFont.truetype("arial.ttf", 24) | |
| except IOError: | |
| _cached_font = ImageFont.load_default() | |
| return _cached_font |
| if hasattr(self, '_cached_hwnd') and self._cached_hwnd is not None: | ||
| if user32.IsWindow(self._cached_hwnd): | ||
| return self._cached_hwnd | ||
| self._cached_hwnd = None |
There was a problem hiding this comment.
Using hasattr repeatedly is less efficient and less idiomatic than using getattr with a default value, especially since _cached_hwnd is not initialized in the constructor. This approach is cleaner and avoids potential AttributeError issues if the attribute is accessed elsewhere.
| if hasattr(self, '_cached_hwnd') and self._cached_hwnd is not None: | |
| if user32.IsWindow(self._cached_hwnd): | |
| return self._cached_hwnd | |
| self._cached_hwnd = None | |
| cached_hwnd = getattr(self, '_cached_hwnd', None) | |
| if cached_hwnd is not None: | |
| if user32.IsWindow(cached_hwnd): | |
| return cached_hwnd | |
| self._cached_hwnd = None |
| mock_ws = MagicMock() | ||
| mock_ws.recv.side_effect = Exception("Connection closed") |
There was a problem hiding this comment.
The test uses MagicMock for mock_ws, but env._handle performs an async for iteration over it. MagicMock does not support async iteration by default, which will cause the test to fail with a TypeError. You should use AsyncMock instead to correctly simulate the websocket's behavior.
| mock_ws = MagicMock() | |
| mock_ws.recv.side_effect = Exception("Connection closed") | |
| from unittest.mock import AsyncMock | |
| mock_ws = AsyncMock() | |
| mock_ws.__aiter__.return_value = [Exception("Connection closed")] # Simulate failure during iteration |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (3)
tests/test_train_multi.py (1)
21-26: ⚡ Quick winMake checkpoint ordering deterministic without
sleep.Using
time.sleep(0.01)to force file timestamp order can be flaky in CI/filesystems with coarse mtime resolution. Set explicit mtimes instead.Proposed deterministic update
+import os +import time @@ - import time f1.touch() - time.sleep(0.01) + base = time.time() + os.utime(f1, (base + 1, base + 1)) f3.touch() - time.sleep(0.01) + os.utime(f3, (base + 2, base + 2)) f2.touch() + os.utime(f2, (base + 3, base + 3))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_train_multi.py` around lines 21 - 26, Replace the flaky time.sleep-based ordering by setting explicit modification times for the files; instead of calling f1.touch(); time.sleep(...); f3.touch(); time.sleep(...); f2.touch(), call os.utime or Path.utime on f1, f3, and f2 with monotonically increasing timestamps (e.g., now, now+1, now+2) so checkpoint ordering is deterministic in tests/test_train_multi.py; remove the sleep calls and use the explicit utime updates targeting the existing f1, f3, f2 symbols.tests/test_notify.py (1)
203-207: ⚡ Quick winAssert the error path is actually exercised.
At Line 205, this currently only checks “no exception.” Add an assertion that the mocked
requests.postwas called so the failure path is truly covered.Proposed test hardening
class TestSetupBotMenu: def test_setup_bot_menu_error(self, fake): - with patch('requests.post', side_effect=Exception('Mock API Error')): + with patch('requests.post', side_effect=Exception('Mock API Error')) as mock_post: # Should not crash fake.setup_bot_menu() + assert mock_post.call_count > 0🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_notify.py` around lines 203 - 207, The test TestSetupBotMenu.test_setup_bot_menu_error currently only ensures no exception is raised; update it to assert the mocked failure path is exercised by assigning the patch to a mock variable (e.g., mock_post = patch('requests.post', side_effect=Exception(...))).start() or use with patch(...) as mock_post: and after calling fake.setup_bot_menu() add an assertion like mock_post.assert_called_once() (or mock_post.assert_called()) to verify requests.post was invoked during setup_bot_menu.tests/test_video_recorder.py (1)
266-277: 💤 Low valueConsider testing
force_triggerfrom additional states.While testing from
cooldownstate validates the bypass behavior, testing fromidle,recording, andpoststates would provide more complete coverage and confirm the method works correctly regardless of the recorder's state.💡 Example expansion
`@pytest.mark.parametrize`("state", ["idle", "recording", "post", "cooldown"]) def test_force_trigger(self, recorder, state): recorder._state = state recorder.force_trigger("force_event", {"info": "data"}) assert not recorder._event_q.empty() item = recorder._event_q.get_nowait() assert item["name"] == "force_event" assert item["ctx"] == {"info": "data"}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_video_recorder.py` around lines 266 - 277, Extend the test_force_trigger case to parametrize over recorder states so we validate force_trigger works from "idle", "recording", "post", as well as "cooldown"; set recorder._state to each state before calling recorder.force_trigger("force_event", {"info":"data"}), then assert recorder._event_q is not empty and verify the queued item via recorder._event_q.get_nowait() has item["name"] == "force_event" and item["ctx"] == {"info":"data"} to ensure behavior is consistent across states.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/test_eval.py`:
- Around line 9-15: The test currently hides a missing implementation by
skipping when calculate_thinking cannot be imported; instead make the test fail
loudly: remove the try/except import wrapper (or keep it but add an explicit
assertion) so that test_calculate_thinking either raises ImportError or asserts
calculate_thinking is not None with a clear failure message. Update references
to the calculate_thinking symbol in test_calculate_thinking so the test will
fail if the function is absent rather than being skipped.
In `@tests/test_noita_env.py`:
- Around line 5-15: The test's make_env() currently writes mocks directly into
sys.modules and never restores them; change it to use unittest.mock.patch.dict
to temporarily inject the mocked modules (keys 'mss', 'pygetwindow', 'pyrect')
into sys.modules so they are automatically restored, perform
importlib.reload(noita_env) inside that patch.dict context, and ensure imports
for patch (e.g., from unittest.mock import patch) are present so the mocking is
scoped and doesn't leak global interpreter state.
In `@tests/test_video_recorder.py`:
- Around line 113-116: The test test_trigger_does_not_crash_on_empty_ctx calls
recorder.trigger_event(event_name, {}) but does not disable cooldown, so the
assertion on recorder._event_q may fail; before calling trigger_event set
recorder.COOLDOWN_SEC = 0 and recorder._last_sent = 0.0 (as done in other tests)
to ensure events are queued regardless of cooldown. Locate this test and add
those two assignments referencing the recorder instance to guarantee
deterministic behavior.
- Around line 252-257: The test test_is_idle is missing coverage for the "post"
state; update the test to set recorder._state = "post" and assert
recorder.is_idle is False so the is_idle property is validated for idle,
recording, post, and cooldown states; locate the test function test_is_idle and
the recorder/is_idle checks to add the new assertion.
- Around line 259-264: The test_status function is missing coverage for the
"post" state; update test_status to set recorder._state = "post" and assert
recorder.status == "post" along with the existing assertions so the test covers
idle → recording → post → idle transitions; modify the test_status method
(referenced by name) to include the "post" state check using recorder._state and
recorder.status.
In `@train.py`:
- Line 16: Remove the top-level "import wandb" so wandb is not imported at
module load; rely on the existing guarded lazy import that checks
cfg.wandb_enabled (the conditional import at/around the code near the guarded
section around line 68). Specifically, delete the top-level import statement and
ensure all references use the lazily imported wandb (or are behind the
cfg.wandb_enabled check) so no import happens when cfg.wandb_enabled is false.
---
Nitpick comments:
In `@tests/test_notify.py`:
- Around line 203-207: The test TestSetupBotMenu.test_setup_bot_menu_error
currently only ensures no exception is raised; update it to assert the mocked
failure path is exercised by assigning the patch to a mock variable (e.g.,
mock_post = patch('requests.post', side_effect=Exception(...))).start() or use
with patch(...) as mock_post: and after calling fake.setup_bot_menu() add an
assertion like mock_post.assert_called_once() (or mock_post.assert_called()) to
verify requests.post was invoked during setup_bot_menu.
In `@tests/test_train_multi.py`:
- Around line 21-26: Replace the flaky time.sleep-based ordering by setting
explicit modification times for the files; instead of calling f1.touch();
time.sleep(...); f3.touch(); time.sleep(...); f2.touch(), call os.utime or
Path.utime on f1, f3, and f2 with monotonically increasing timestamps (e.g.,
now, now+1, now+2) so checkpoint ordering is deterministic in
tests/test_train_multi.py; remove the sleep calls and use the explicit utime
updates targeting the existing f1, f3, f2 symbols.
In `@tests/test_video_recorder.py`:
- Around line 266-277: Extend the test_force_trigger case to parametrize over
recorder states so we validate force_trigger works from "idle", "recording",
"post", as well as "cooldown"; set recorder._state to each state before calling
recorder.force_trigger("force_event", {"info":"data"}), then assert
recorder._event_q is not empty and verify the queued item via
recorder._event_q.get_nowait() has item["name"] == "force_event" and item["ctx"]
== {"info":"data"} to ensure behavior is consistent across states.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d0f7c90e-b34d-44ef-a95e-6d66b205a27a
📒 Files selected for processing (14)
callbacks.pyconfig.pyeval.pynotify.pytests/test_callbacks.pytests/test_eval.pytests/test_noita_env.pytests/test_notify.pytests/test_offline_analysis.pytests/test_train.pytests/test_train_multi.pytests/test_video_recorder.pytrain.pyvideo_recorder.py
💤 Files with no reviewable changes (2)
- callbacks.py
- eval.py
| try: | ||
| from eval import calculate_thinking | ||
| except ImportError: | ||
| calculate_thinking = None | ||
|
|
||
| @pytest.mark.skipif(calculate_thinking is None, reason="calculate_thinking not found") | ||
| def test_calculate_thinking(): |
There was a problem hiding this comment.
Don’t skip this test when calculate_thinking is missing.
Current skip logic turns a broken/missing target function into a green test run, which weakens coverage for this path.
Suggested fix
-try:
- from eval import calculate_thinking
-except ImportError:
- calculate_thinking = None
-
-@pytest.mark.skipif(calculate_thinking is None, reason="calculate_thinking not found")
+from eval import calculate_thinking
def test_calculate_thinking():📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try: | |
| from eval import calculate_thinking | |
| except ImportError: | |
| calculate_thinking = None | |
| @pytest.mark.skipif(calculate_thinking is None, reason="calculate_thinking not found") | |
| def test_calculate_thinking(): | |
| from eval import calculate_thinking | |
| def test_calculate_thinking(): |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/test_eval.py` around lines 9 - 15, The test currently hides a missing
implementation by skipping when calculate_thinking cannot be imported; instead
make the test fail loudly: remove the try/except import wrapper (or keep it but
add an explicit assertion) so that test_calculate_thinking either raises
ImportError or asserts calculate_thinking is not None with a clear failure
message. Update references to the calculate_thinking symbol in
test_calculate_thinking so the test will fail if the function is absent rather
than being skipped.
| def make_env(): | ||
| import importlib, types, sys | ||
|
|
||
| # Mock problematic modules | ||
| sys.modules['mss'] = MagicMock() | ||
| sys.modules['pygetwindow'] = MagicMock() | ||
| sys.modules['pyrect'] = MagicMock() | ||
|
|
||
| import noita_env | ||
| importlib.reload(noita_env) | ||
|
|
There was a problem hiding this comment.
Avoid leaking mocked modules into global interpreter state.
make_env() writes directly into sys.modules and never restores it. That can contaminate later tests and create order-dependent failures. Wrap this in patch.dict(...): so mocks are scoped.
Suggested fix
def make_env():
import importlib, types, sys
- # Mock problematic modules
- sys.modules['mss'] = MagicMock()
- sys.modules['pygetwindow'] = MagicMock()
- sys.modules['pyrect'] = MagicMock()
-
- import noita_env
- importlib.reload(noita_env)
+ mocked_modules = {
+ 'mss': MagicMock(),
+ 'pygetwindow': MagicMock(),
+ 'pyrect': MagicMock(),
+ }
+ with patch.dict(sys.modules, mocked_modules):
+ import noita_env
+ importlib.reload(noita_env)
env = noita_env.NoitaEnv.__new__(noita_env.NoitaEnv)
env._lock = MagicMock()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/test_noita_env.py` around lines 5 - 15, The test's make_env() currently
writes mocks directly into sys.modules and never restores them; change it to use
unittest.mock.patch.dict to temporarily inject the mocked modules (keys 'mss',
'pygetwindow', 'pyrect') into sys.modules so they are automatically restored,
perform importlib.reload(noita_env) inside that patch.dict context, and ensure
imports for patch (e.g., from unittest.mock import patch) are present so the
mocking is scoped and doesn't leak global interpreter state.
| @pytest.mark.parametrize("event_name", ALL_EVENTS) | ||
| def test_trigger_does_not_crash_on_empty_ctx(self, recorder, event_name): | ||
| recorder.trigger_event(event_name, {}) | ||
| assert not recorder._event_q.empty() |
There was a problem hiding this comment.
Test may fail intermittently due to missing cooldown setup.
The assertion assert not recorder._event_q.empty() will fail if the recorder is in cooldown, but this test doesn't disable cooldown like other tests in the file. Compare with lines 167-171 and 226-230, which explicitly set recorder.COOLDOWN_SEC = 0 and recorder._last_sent = 0.0 to ensure events are queued.
🔧 Proposed fix
`@pytest.mark.parametrize`("event_name", ALL_EVENTS)
def test_trigger_does_not_crash_on_empty_ctx(self, recorder, event_name):
+ recorder.COOLDOWN_SEC = 0
+ recorder._last_sent = 0.0
recorder.trigger_event(event_name, {})
assert not recorder._event_q.empty()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @pytest.mark.parametrize("event_name", ALL_EVENTS) | |
| def test_trigger_does_not_crash_on_empty_ctx(self, recorder, event_name): | |
| recorder.trigger_event(event_name, {}) | |
| assert not recorder._event_q.empty() | |
| `@pytest.mark.parametrize`("event_name", ALL_EVENTS) | |
| def test_trigger_does_not_crash_on_empty_ctx(self, recorder, event_name): | |
| recorder.COOLDOWN_SEC = 0 | |
| recorder._last_sent = 0.0 | |
| recorder.trigger_event(event_name, {}) | |
| assert not recorder._event_q.empty() |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/test_video_recorder.py` around lines 113 - 116, The test
test_trigger_does_not_crash_on_empty_ctx calls
recorder.trigger_event(event_name, {}) but does not disable cooldown, so the
assertion on recorder._event_q may fail; before calling trigger_event set
recorder.COOLDOWN_SEC = 0 and recorder._last_sent = 0.0 (as done in other tests)
to ensure events are queued regardless of cooldown. Locate this test and add
those two assignments referencing the recorder instance to guarantee
deterministic behavior.
| def test_is_idle(self, recorder): | ||
| assert recorder.is_idle is True | ||
| recorder._state = "recording" | ||
| assert recorder.is_idle is False | ||
| recorder._state = "cooldown" | ||
| assert recorder.is_idle is False |
There was a problem hiding this comment.
Incomplete state coverage: missing "post" state.
Line 10 documents the state machine as idle → recording → post → idle, but this test only checks idle, recording, and cooldown. The post state should also be tested to ensure is_idle returns the expected value.
📝 Proposed fix to add "post" state coverage
def test_is_idle(self, recorder):
assert recorder.is_idle is True
recorder._state = "recording"
assert recorder.is_idle is False
+ recorder._state = "post"
+ assert recorder.is_idle is False
recorder._state = "cooldown"
assert recorder.is_idle is False🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/test_video_recorder.py` around lines 252 - 257, The test test_is_idle
is missing coverage for the "post" state; update the test to set recorder._state
= "post" and assert recorder.is_idle is False so the is_idle property is
validated for idle, recording, post, and cooldown states; locate the test
function test_is_idle and the recorder/is_idle checks to add the new assertion.
| def test_status(self, recorder): | ||
| assert recorder.status == "idle" | ||
| recorder._state = "recording" | ||
| assert recorder.status == "recording" | ||
| recorder._state = "cooldown" | ||
| assert recorder.status == "cooldown" |
There was a problem hiding this comment.
Incomplete state coverage: missing "post" state.
This test should include the post state to match the documented state machine (line 10: idle → recording → post → idle).
📝 Proposed fix to add "post" state coverage
def test_status(self, recorder):
assert recorder.status == "idle"
recorder._state = "recording"
assert recorder.status == "recording"
+ recorder._state = "post"
+ assert recorder.status == "post"
recorder._state = "cooldown"
assert recorder.status == "cooldown"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def test_status(self, recorder): | |
| assert recorder.status == "idle" | |
| recorder._state = "recording" | |
| assert recorder.status == "recording" | |
| recorder._state = "cooldown" | |
| assert recorder.status == "cooldown" | |
| def test_status(self, recorder): | |
| assert recorder.status == "idle" | |
| recorder._state = "recording" | |
| assert recorder.status == "recording" | |
| recorder._state = "post" | |
| assert recorder.status == "post" | |
| recorder._state = "cooldown" | |
| assert recorder.status == "cooldown" |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/test_video_recorder.py` around lines 259 - 264, The test_status
function is missing coverage for the "post" state; update test_status to set
recorder._state = "post" and assert recorder.status == "post" along with the
existing assertions so the test covers idle → recording → post → idle
transitions; modify the test_status method (referenced by name) to include the
"post" state check using recorder._state and recorder.status.
| import argparse | ||
| import os | ||
| import sys | ||
| import wandb |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify that wandb is currently imported at module load time.
rg -nP '^\s*import wandb\b' train.py
rg -nP '\bif\s+not\s+cfg\.wandb_enabled\b|\bimport wandb\b' train.pyRepository: yava-code/noitarl
Length of output: 240
🏁 Script executed:
#!/bin/bash
# Get context around the imports and cfg.wandb_enabled check
head -80 train.py | cat -nRepository: yava-code/noitarl
Length of output: 2663
Keep wandb optional by removing the top-level import.
The import at line 16 executes at module load time, before cfg.wandb_enabled is checked. This causes startup failure when W&B is disabled and not installed. Remove the top-level import and rely on the guarded lazy import at line 68.
Suggested fix
-import wandb📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import wandb |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@train.py` at line 16, Remove the top-level "import wandb" so wandb is not
imported at module load; rely on the existing guarded lazy import that checks
cfg.wandb_enabled (the conditional import at/around the code near the guarded
section around line 68). Specifically, delete the top-level import statement and
ensure all references use the lazily imported wandb (or are behind the
cfg.wandb_enabled check) so no import happens when cfg.wandb_enabled is false.
Optional,annotations,field_validator.notify.pyby loading the font once and reusing it.notify.pyby caching the HWND.video_recorder.pyusingcollections.deque.generate_ai_statusempty API key intests/test_notify.py.video_recorder.py(is_idle,status,force_trigger).test_train.py,test_noita_env.py,test_train_multi.py,test_eval.py,test_offline_analysis.py,test_callbacks.py.setup_bot_menuerror path intests/test_notify.py.PR created automatically by Jules for task 5215528783929094644 started by @yava-code
Summary by CodeRabbit
Tests
Performance
Chores